package com.linktco.resource.custom;


import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.linktco.resource.builder.oss.OssBuilder;
import com.linktco.resource.entity.Oss;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Part;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.oss.enums.PolicyType;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.FileUtil;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by TD on 2021/10/20
 */
@Slf4j
@Component
public class CustomMinioTemplate {

    private CustomMinioClient client;

    @Autowired
    private OssBuilder ossBuilder;

    /**
     * 初始化默认存储桶
     */
    @PostConstruct
    public void initMinioClient() {
        Oss oss = ossBuilder.getOss("000000", "minio");
        MinioClient minioClient = MinioClient.builder()
                .endpoint(oss.getEndpoint())
                .credentials(oss.getAccessKey(), oss.getSecretKey())
                .build();
        client = new CustomMinioClient(minioClient);
    }



    /**
     * 返回临时带签名、过期时间一天、Get请求方式的访问URL
     *
     * @param bucketName  桶名
     * @param ossFilePath Oss文件路径
     * @return
     */
    @SneakyThrows
    public String getPresignedObjectUrl(String bucketName, String ossFilePath, Map<String, String> queryParams) {
        return client.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.PUT)
                        .bucket(bucketName)
                        .object(ossFilePath)
                        .expiry(60 * 60 * 24)
                        .extraQueryParams(queryParams)
                        .build());
    }




    /**
     * 上传分片上传请求，返回uploadId
     */
    public CreateMultipartUploadResponse uploadId(String bucketName, String region, String objectName, Multimap<String, String> headers, Multimap<String, String> extraQueryParams) throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException {
        return client.createMultipartUpload(bucketName, region, objectName, headers, extraQueryParams);
    }

    /**
     * 完成分片上传，执行合并文件
     *
     * @param bucketName       存储桶
     * @param region           区域
     * @param objectName       对象名
     * @param uploadId         上传ID
     * @param parts            分片
     * @param extraHeaders     额外消息头
     * @param extraQueryParams 额外查询参数
     */
    public ObjectWriteResponse completeMultipartUpload(String bucketName, String region, String objectName, String uploadId, Part[] parts, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams) throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException {
        return client.completeMultipartUpload(bucketName, region, objectName, uploadId, parts, extraHeaders, extraQueryParams);
    }


    /**
     * 查询分片数据
     *
     * @param bucketName       存储桶
     * @param region           区域
     * @param objectName       对象名
     * @param uploadId         上传ID
     * @param extraHeaders     额外消息头
     * @param extraQueryParams 额外查询参数
     */
    public ListPartsResponse listMultipart(String bucketName, String region, String objectName, Integer maxParts, Integer partNumberMarker, String uploadId, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams) throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException {
        return client.listMultipart(bucketName, region, objectName, maxParts, partNumberMarker, uploadId, extraHeaders, extraQueryParams);
    }

    public String fileName(String originalFilename) {
        return "upload/" + DateUtil.today() + "/" + StringUtil.randomUUID() + "." + FileUtil.getFileExtension(originalFilename);
    }

    public Map<String, Object> initMultiPartUpload(String bucketName, String originalFilename, Integer chunkSize) throws Exception {
        makeBucket(bucketName);
        String contentType = "application/octet-stream";
        HashMultimap<String, String> headers = HashMultimap.create();
        headers.put("Content-Type", contentType);

        String fileName = fileName(originalFilename);
        CreateMultipartUploadResponse response = uploadId(bucketName, null, fileName, headers, null);
        String uploadId = response.result().uploadId();

        Map<String, String> reqParams = new HashMap<>(3);
        reqParams.put("uploadId", uploadId);

        List<String> partList = new ArrayList<>();

        for (int i = 1; i <= chunkSize; i++) {
            reqParams.put("partNumber", String.valueOf(i));
            String uploadUrl = getPresignedObjectUrl(bucketName, fileName, reqParams);
            partList.add(uploadUrl);
        }

        Oss oss = ossBuilder.getOss("000000", "minio");
        return new HashMap<String, Object>(6) {{
            put("uploadId",uploadId);
            put("link", String.format("%s/%s/%s", replaceEndpoint(oss.getEndpoint()), bucketName, fileName));
            put("domain", String.format("%s/%s", replaceEndpoint(oss.getEndpoint()), bucketName));
            put("originalName", originalFilename);
            put("name", fileName);
            put("urlList", partList);
        }};

    }

    private String replaceEndpoint(String endpoint) {

        return endpoint;
    }


    public String mergeMultipartUpload(String fileName, String uploadId, String bucketName) {
        try {
            Part[] parts = new Part[10000];
            ListPartsResponse partResult = listMultipart(bucketName, null, fileName, 10000, 0, uploadId, null, null);
            int partNumber = 1;
            List<Part> partList = partResult.result().partList();
            for (int i = 0; i < partList.size(); i++) {
                parts[partNumber - 1] = new Part(partNumber, partList.get(i).etag());
                partNumber++;
            }
            completeMultipartUpload(bucketName, null, fileName, uploadId, parts, null, null);
            return getPresignedObjectUrl(bucketName, fileName, null);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }




    @SneakyThrows
    public void makeBucket(String bucketName) {
        try {
            if (!this.client.bucketExists((BucketExistsArgs.builder().bucket(bucketName)).build())) {
                this.client.makeBucket((MakeBucketArgs.builder().bucket(bucketName)).build());
                this.client.setBucketPolicy((SetBucketPolicyArgs.builder().bucket(bucketName)).config(getPolicyType(bucketName, PolicyType.READ)).build());
            }

        } catch (Throwable var3) {
            throw var3;
        }
    }
    private static String getPolicyType(String bucketName, PolicyType policyType) {
        StringBuilder builder = new StringBuilder();
        builder.append("{\n");
        builder.append("    \"Statement\": [\n");
        builder.append("        {\n");
        builder.append("            \"Action\": [\n");
        switch(policyType) {
            case WRITE:
                builder.append("                \"s3:GetBucketLocation\",\n");
                builder.append("                \"s3:ListBucketMultipartUploads\"\n");
                break;
            case READ_WRITE:
                builder.append("                \"s3:GetBucketLocation\",\n");
                builder.append("                \"s3:ListBucket\",\n");
                builder.append("                \"s3:ListBucketMultipartUploads\"\n");
                break;
            default:
                builder.append("                \"s3:GetBucketLocation\"\n");
        }

        builder.append("            ],\n");
        builder.append("            \"Effect\": \"Allow\",\n");
        builder.append("            \"Principal\": \"*\",\n");
        builder.append("            \"Resource\": \"arn:aws:s3:::");
        builder.append(bucketName);
        builder.append("\"\n");
        builder.append("        },\n");
        if (PolicyType.READ.equals(policyType)) {
            builder.append("        {\n");
            builder.append("            \"Action\": [\n");
            builder.append("                \"s3:ListBucket\"\n");
            builder.append("            ],\n");
            builder.append("            \"Effect\": \"Deny\",\n");
            builder.append("            \"Principal\": \"*\",\n");
            builder.append("            \"Resource\": \"arn:aws:s3:::");
            builder.append(bucketName);
            builder.append("\"\n");
            builder.append("        },\n");
        }

        builder.append("        {\n");
        builder.append("            \"Action\": ");
        switch(policyType) {
            case WRITE:
                builder.append("[\n");
                builder.append("                \"s3:AbortMultipartUpload\",\n");
                builder.append("                \"s3:DeleteObject\",\n");
                builder.append("                \"s3:ListMultipartUploadParts\",\n");
                builder.append("                \"s3:PutObject\"\n");
                builder.append("            ],\n");
                break;
            case READ_WRITE:
                builder.append("[\n");
                builder.append("                \"s3:AbortMultipartUpload\",\n");
                builder.append("                \"s3:DeleteObject\",\n");
                builder.append("                \"s3:GetObject\",\n");
                builder.append("                \"s3:ListMultipartUploadParts\",\n");
                builder.append("                \"s3:PutObject\"\n");
                builder.append("            ],\n");
                break;
            default:
                builder.append("\"s3:GetObject\",\n");
        }

        builder.append("            \"Effect\": \"Allow\",\n");
        builder.append("            \"Principal\": \"*\",\n");
        builder.append("            \"Resource\": \"arn:aws:s3:::");
        builder.append(bucketName);
        builder.append("/*\"\n");
        builder.append("        }\n");
        builder.append("    ],\n");
        builder.append("    \"Version\": \"2012-10-17\"\n");
        builder.append("}\n");
        return builder.toString();
    }


}
